/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { processOptions, throttle, deepEqual } from './utils';
import beacon from './beacon';
import { nextTick } from 'vue';

class VisibilityState {
  public el: any;
  public observer: any;
  public frozen = false;
  public options: any;
  public callback: any;
  public oldResult: any;
  public eventCode = '';
  public direct = false;
  public params = {};
  public constructor(el: any, binding: { value: any }, vnode: any) {
    const { value } = binding;
    this.el = el;
    this.observer = null;
    this.frozen = false;
    this.eventCode = value.EventCode;
    this.direct = value.Direct;
    this.params = value.Params || {};
    this.createObserver(value, vnode);
  }

  public get threshold() {
    return this.options.intersection ? this.options.intersection.threshold : 0;
  }

  public createObserver(options: any, vnode: any) {
    if (this.observer) {
      this.destroyObserver();
    }

    if (this.frozen) return;

    this.options = processOptions(options);

    this.callback = (result: boolean, entry: any) => {
      // this.options.callback(result, entry);
      // 这里调用上报
      if (result === true) {
        beacon.report(this.eventCode, this.params, this.direct);
        // this.options.callback(this.eventCode, this.params, this.direct);
        // console.log(this.eventCode, this.params, this.direct);
      }
      if (result && this.options.once) {
        this.frozen = true;
        this.destroyObserver();
      }
    };
    // Throttle
    if (this.callback && this.options.throttle) {
      const { leading } = this.options.throttleOptions || {};
      this.callback = throttle(this.callback, this.options.throttle, {
        leading: (state: any) => leading === 'both' || (leading === 'visible' && state) || (leading === 'hidden' && !state),
      });
    }

    this.oldResult = undefined;

    this.observer = new IntersectionObserver((entries) => {
      let entry = entries[0];

      if (entries.length > 1) {
        const intersectingEntry = entries.find(e => e.isIntersecting);
        if (intersectingEntry) {
          entry = intersectingEntry;
        }
      }

      if (this.callback) {
        // Use isIntersecting if possible because browsers can report isIntersecting as true,
        // but intersectionRatio as 0, when something very slowly enters the viewport.
        const result = entry.isIntersecting && entry.intersectionRatio >= this.threshold;
        if (result === this.oldResult) return;
        this.oldResult = result;
        this.callback(result, entry);
      }
    }, this.options.intersection);

    // Wait for the element to be in document
    nextTick(() => {
      if (this.observer) {
        this.observer.observe(this.el);
      }
    });
  }

  public destroyObserver() {
    if (this.observer) {
      this.observer.disconnect();
      this.observer = null;
    }

    // Cancel throttled call
    if (this?.callback?._clear) {
      this.callback._clear();
      this.callback = null;
    }
  }
}

const beforeMount = (el: any, binding: any, vnode: any) => {
  const elTmp: any = el;
  const { value } = binding;
  // console.log(value);
  if (!value) return;
  if (typeof IntersectionObserver === 'undefined') {
    console.warn('[vue-observe-visibility] IntersectionObserver API is not available in your browser. Please install this polyfill: https://github.com/w3c/IntersectionObserver/tree/master/polyfill');
  } else {
    const state = new VisibilityState(el, binding, vnode);
    elTmp._vue_visibilityState = state;
  }
};

const beforeUnmount = (el: any) => {
  const elTmp: any = el;
  const state = el._vue_visibilityState;
  if (state) {
    state.destroyObserver();
    delete elTmp._vue_visibilityState;
  }
};

const update = (el: any, binding: any, vnode: any) => {
  const { value, oldValue } = binding;
  if (deepEqual(value, oldValue)) return;
  const state = el._vue_visibilityState;
  if (!value) {
    beforeUnmount(el);
    return;
  }
  if (state) {
    state.createObserver(value, vnode);
  } else {
    beforeMount(el, { value }, vnode);
  }
};

export default {
  beforeMount,
  update,
  beforeUnmount,
};
